﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WP8_Client.CustomObjects;


namespace WP8_Client
{
    public class ClientCore
    {
        Socket cSocket;
        byte[] cBuffer = new byte[1024];
        IPEndPoint cServerEndPoint;
        String cPrivateKey = null;
        SocketAsyncEventArgs cSocketEventArg;
        Int32 User_Id = -1;
        List<CacheItem> cMessageCache;
        BackgroundWorker cCacheWorker;

        public Boolean IsConnected { get; private set; }

        public delegate void NotificationEventHandler(String pNotification);
        public delegate void ErrorEventHandler(String pError);
        public delegate void EmptyEventHandler();
        public event EmptyEventHandler OnConnectionEstablished;
        public event EmptyEventHandler OnLogedIn_Confirmation;
        public event EmptyEventHandler OnLogedIn_Decline;
        public event EmptyEventHandler OnAddContact_Confirmation;
        public event EmptyEventHandler OnCreateUser_Confirmation;
        public event EmptyEventHandler OnCreateUser_Decline;

        public ClientCore()
        {
            this.cMessageCache = new List<CacheItem>();
            
            this.cCacheWorker = new BackgroundWorker();
            this.cCacheWorker.DoWork += cCacheWorker_DoWork;
            this.cCacheWorker.RunWorkerCompleted += (obj, e) => { this.cCacheWorker.RunWorkerAsync(this.cMessageCache); };
            this.cCacheWorker.RunWorkerAsync(this.cMessageCache);

            this.cSocketEventArg = new SocketAsyncEventArgs();
            this.cSocketEventArg.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 1337);
            this.cSocketEventArg.SetBuffer(this.cBuffer, 0, this.cBuffer.Length);
            this.cSocketEventArg.Completed += socketEventArg_Completed;

            this.cServerEndPoint = new IPEndPoint(IPAddress.Parse(Globals.Network.ServerIp), Globals.Network.Port);
            this.cSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            this.cSocket.Bind(new IPEndPoint(IPAddress.Any, Globals.Network.Port));
            this.cSocket.ReceiveFromAsync(this.cSocketEventArg);

            //get privateKey from Server            
            this.SendMessage(new Message(Message.eCommand.GetSecretKey, -1, -1, Globals.Security.PublicKey));            
        }

        void socketEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            this.cSocket.ReceiveFromAsync(this.cSocketEventArg);

            if (e.SocketError == SocketError.Success)
            {
                // Retrieve the data from the buffer convert to MessageObj and process
                this.ProcessMessage(new Message(Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred)));
            }
            else
            {
                //TODO log exception
                //e.SocketError.ToString();
            }
        }

        void cCacheWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            List<CacheItem> _CacheItems = (List<CacheItem>)e.Argument;

            foreach (CacheItem _CacheItem in _CacheItems)
            {                
                this.SendMessage(_CacheItem.Message, false);
            }

            Thread.Sleep(3000);
        }

        public Boolean SendMessage(Message pMessage, Boolean pCache = true)
        {
            if (this.cSocket != null)
            {
                try
                {
                    SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
                    socketEventArg.RemoteEndPoint = this.cServerEndPoint;
                    byte[] _bMessage = Encoding.UTF8.GetBytes(pMessage.ToString());
                    socketEventArg.SetBuffer(_bMessage, 0, _bMessage.Length);
                    this.cSocket.SendToAsync(socketEventArg);

                    if (pCache)                    
                        this.cMessageCache.Add(new CacheItem(pMessage));

                    return true;

                }
                catch (Exception)
                {                    
                    //TODO log exception
                    return false;
                }
            }

            return false;
        }

        public void ProcessMessage(Message pMessage)
        {
            #region Cached
            if (pMessage.Command == Message.eCommand.Confirmation || pMessage.Command == Message.eCommand.Decline)
            {
                CacheItem _CacheItem = this.cMessageCache.Find( ci => ci.Message.GUID == pMessage.RequestGUID);                

                if (_CacheItem != null)
                {
                    this.cMessageCache.Remove(_CacheItem);

                    switch (_CacheItem.Message.Command)
                    {
                        case Message.eCommand.LogIn:
                            if (pMessage.Command == Message.eCommand.Confirmation)
                            {                                
                                this.User_Id = pMessage.TargetID;
                                if (this.OnLogedIn_Confirmation != null)
                                    this.OnLogedIn_Confirmation();

                                //this.cLocationWorker.RunWorkerAsync();
                            }
                            else
                            {
                                if (this.OnLogedIn_Decline != null)
                                    this.OnLogedIn_Decline();
                            }                            
                            break;
                        case Message.eCommand.CreateUser:
                            if (pMessage.Command == Message.eCommand.Confirmation)
                            {
                                this.User_Id = pMessage.TargetID;
                                if (this.OnCreateUser_Confirmation != null)
                                    this.OnCreateUser_Confirmation();
                            }
                            else
                            {
                                if (this.OnCreateUser_Decline != null)
                                    this.OnCreateUser_Decline();
                            }
                            break;
                        case Message.eCommand.AddContact:
                            if (this.OnAddContact_Confirmation != null)
                                this.OnAddContact_Confirmation();
                            break;
                        case Message.eCommand.GetSecretKey:
                            this.cPrivateKey = pMessage.Content;
                            this.IsConnected = true;
                            if (this.OnConnectionEstablished != null)
                                this.OnConnectionEstablished();
                            break;
                        default:
                            break;
                    }                    
                    return;
                }
            }
            #endregion

            switch (pMessage.Command)
            {
                case Message.eCommand.ContactAdded:
                    this.SendMessage(new Message(Message.eCommand.Confirmation, this.User_Id, -1, "", pMessage.GUID), false);
                    String _tmp = pMessage.Content;
                    break;

                default:
                    break;
            }
        }

        public void Close()
        {
            if (this.cSocket != null)
            {
                this.cSocket.Close();
            }
        }

        public void LogIn(String pUsername, String pPassword)
        {
            this.cMessageCache.RemoveAll(item => item.Message.Command == Message.eCommand.LogIn);
            this.SendMessage(new Message(Message.eCommand.LogIn, -1, -1, pUsername + Globals.Separator.Content + pPassword));
        }

        public void RegisterNewUser(String pUsername, String pPassword)
        {
            this.SendMessage(new Message(Message.eCommand.CreateUser, -1, -1, pUsername + Globals.Separator.Content + pPassword));
        }

        public void AddContact(String pUserName = "loki")
        {
            this.SendMessage(new Message(Message.eCommand.AddContact, this.User_Id, -1, pUserName + Globals.Separator.Content + "Hier könnte Ihre Werbung stehen."));
        }

        public void UpdateLocation(Location pLocation)
        {
            this.SendMessage(new Message(Message.eCommand.Location, User_Id, -1, pLocation.Latitude.ToString().Replace(",",".") + Globals.Separator.Content + pLocation.Longitude.ToString().Replace(",",".")), false);
        }   






        public class Message
        {
            public enum eCommand
            {
                Debug, Feedback, Text, Location, Picture,
                Reply, Confirmation, Decline, LogIn, LogOut,
                CreateUser, DeleteUser, RenameUser,
                GetContacts, AddContact, RemoveContact, WantToAddYouAsContact, ContactAdded,
                GetSecretKey
            }

            public eCommand Command { get; private set; }
            public String GUID { get; private set; }
            public String RequestGUID { get; private set; }
            public Int32 SourceID { get; private set; }
            public Int32 TargetID { get; private set; }
            public EndPoint TargetEndPoint { get; private set; }
            public String Content { get; private set; }

            public Message(eCommand pCommand, Int32 pSourceID, Int32 pTargetID, String pContent, String pRequestGUID = "", EndPoint pTargetEndpoint = null)
            {
                this.Command = pCommand;
                this.GUID = Guid.NewGuid().ToString();
                this.RequestGUID = pRequestGUID;
                this.SourceID = pSourceID;
                this.TargetID = pTargetID;
                this.TargetEndPoint = pTargetEndpoint;
                this.Content = pContent;
            }

            public Message(String pRawMessage)
            {
                if (pRawMessage != null && pRawMessage.Length > 0 && pRawMessage.Contains("|#|"))
                {
                    String[] MessageArray = pRawMessage.Split(new string[] { "|#|" }, StringSplitOptions.None);

                    if (MessageArray.Length == 6)
                    {
                        // check if COMMAND and GUID is set
                        if (MessageArray[0] != "" || MessageArray[1] != "")
                        {
                            this.Command = (eCommand)Enum.Parse(typeof(eCommand), MessageArray[0]);
                            this.GUID = MessageArray[1];
                            this.RequestGUID = MessageArray[2];
                            this.SourceID = Convert.ToInt32(MessageArray[3]);
                            this.TargetID = Convert.ToInt32(MessageArray[4]);
                            this.Content = MessageArray[5];
                        }
                        else
                        {
                            this.GUID = "-1";
                            //Log.WriteLine("COMMAND or GUID invalid or not set");
                        }
                    }
                    else
                    {
                        this.GUID = "-1";
                        //Log.WriteLine("Message has not enough params");
                    }
                }
                else
                {
                    this.GUID = "-1";
                    //Log.WriteLine("Message invalid");
                }
            }

            public override string ToString()
            {
                String _Message = ((int)Command).ToString();                        // add command
                _Message += Globals.Separator.Message + GUID;                       // add GUID
                _Message += Globals.Separator.Message + RequestGUID;                // add Requested Message GUID
                _Message += Globals.Separator.Message + SourceID.ToString();        // add SourceUserId
                _Message += Globals.Separator.Message + TargetID.ToString();        // add TargetUserId
                _Message += Globals.Separator.Message + Content;                    // add content
                return _Message;
            }
        }
    }
}
